/*
 * Copyright (c) 2020 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.devio.simple;

import ohos.aafwk.ability.Ability;
import ohos.aafwk.content.Intent;
import ohos.agp.components.*;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.components.surfaceprovider.SurfaceProvider;
import ohos.agp.graphics.Surface;
import ohos.agp.graphics.SurfaceOps;
import ohos.agp.window.dialog.ToastDialog;
import ohos.app.Context;
import ohos.app.Environment;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.media.camera.CameraKit;
import ohos.media.camera.device.Camera;
import ohos.media.camera.device.CameraConfig;
import ohos.media.camera.device.CameraStateCallback;
import ohos.media.camera.device.FrameConfig;
import ohos.media.image.ImageReceiver;
import ohos.media.image.common.ImageFormat;
import ohos.security.SystemPermission;
import org.devio.takephoto.uitl.TConstant;
import org.devio.takephoto.uitl.UiUtil;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

import static ohos.bundle.IBundleManager.PERMISSION_GRANTED;
import static ohos.media.camera.device.Camera.FrameConfigType.FRAME_CONFIG_PICTURE;
import static ohos.media.camera.device.Camera.FrameConfigType.FRAME_CONFIG_PREVIEW;

public class CameraAbility extends Ability {
    private static final String TAG = Camera.class.getSimpleName();
    private static final int PERMISSION_REQUEST_CODE = 1, SCREEN_WIDTH = 1080, SCREEN_HEIGHT = 2340, REQUEST_CODE = 1021, IMAGE_RCV_CAPACITY = 5, NAVIGATION_TIMER = 1500;
    private Surface previewSurface;
    private SurfaceProvider surfaceProvider;
    private boolean isCameraRear;
    private Camera cameraDevice;
    private EventHandler creamEventHandler;
    private ImageReceiver imageReceiver;
    private File targetFile;
    private Context mContext;
    private ToastDialog toastDialog;

    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        setUIContent(ResourceTable.Layout_home_layout);
        requestCameraPermission();
        getWindow().setTransparent(true);
        mContext = this;
        initSurface();
        initControlComponents();

        creamEventHandler = new EventHandler(EventRunner.create("CameraBackground"));
    }

    /**
     * This method is used to init camera surface
     */
    private void initSurface() {
        surfaceProvider = new SurfaceProvider(this);
        DirectionalLayout.LayoutConfig params = new DirectionalLayout.LayoutConfig(
                ComponentContainer.LayoutConfig.MATCH_PARENT, ComponentContainer.LayoutConfig.MATCH_PARENT);

        surfaceProvider.setLayoutConfig(params);
        surfaceProvider.pinToZTop(false);

        surfaceProvider.getSurfaceOps().get().addCallback(new SurfaceCallBack());

        ((ComponentContainer) findComponentById(ResourceTable.Id_surface_container)).addComponent(surfaceProvider);
    }

    /**
     * This method is used to initialize buttons & their click events
     */
    private void initControlComponents() {
        findComponentById(ResourceTable.Id_tack_picture_btn).setClickedListener(this::takePicture);
        findComponentById(ResourceTable.Id_switch_camera_btn).setClickedListener(component -> switchClicked());
    }

    /**
     * This method is used to terminate the ability after taking & saving picture from camera
     *
     * @throws IOException
     */
    private void terminate() throws IOException {
        Intent intent = new Intent();
        if (targetFile != null) {
            intent.setParam(TConstant.REQUEST_FILE_PATH, targetFile.getCanonicalPath());
        } else {
            intent.setParam(TConstant.REQUEST_FILE_PATH, "");
        }
        setResult(REQUEST_CODE, intent);
        terminateAbility();
    }

    /**
     * This method is used to capture picture from camera
     *
     * @param con clicked button component
     */
    private void takePicture(Component con) {
        FrameConfig.Builder framePictureConfigBuilder = cameraDevice.getFrameConfigBuilder(FRAME_CONFIG_PICTURE);
        framePictureConfigBuilder.addSurface(imageReceiver.getRecevingSurface());
        FrameConfig pictureFrameConfig = framePictureConfigBuilder.build();
        cameraDevice.triggerSingleCapture(pictureFrameConfig);
    }

    /**
     * This method is used to switch camera from back-to-front & vise-versa
     */
    private void switchClicked() {
        isCameraRear = !isCameraRear;
        openCamera();
    }

    @Override
    protected void onStop() {
        releaseCamera();
    }

    /**
     * This method is used to open camera interface using CameraKit
     */
    private void openCamera() {
        imageReceiver = ImageReceiver.create(SCREEN_WIDTH, SCREEN_HEIGHT, ImageFormat.JPEG, IMAGE_RCV_CAPACITY);
        imageReceiver.setImageArrivalListener(this::saveImage);

        CameraKit cameraKit = CameraKit.getInstance(getApplicationContext());
        String[] cameraList = cameraKit.getCameraIds();
        String cameraId = cameraList.length > 1 && isCameraRear ? cameraList[1] : cameraList[0];
        CameraStateCallbackImpl cameraStateCallback = new CameraStateCallbackImpl();
        cameraKit.createCamera(cameraId, cameraStateCallback, creamEventHandler);
    }

    /**
     * This method is used to save picture taken by camera
     *
     * @param receiver An image receiver that connects to an image output device and provides a buffer queue to receive image data.
     */
    private void saveImage(ImageReceiver receiver) {
        String fileName = System.currentTimeMillis() + TConstant.IMG_FILE_TYPE;
        targetFile = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), fileName);
        ohos.media.image.Image.Component component =
                receiver.readNextImage().getComponent(ImageFormat.ComponentType.JPEG);
        byte[] bytes = new byte[component.remaining()];
        component.read(bytes);

        try (FileOutputStream output = new FileOutputStream(targetFile)) {
            output.write(bytes);
            output.close();
            showTips(mContext, ResourceTable.String_picture_saved_path +"" + targetFile);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * This method is used to release camera interface after taking & saving picture
     */
    private void releaseCamera() {
        if (cameraDevice != null) {
            cameraDevice.release();
            cameraDevice = null;
        }

        if (imageReceiver != null) {
            imageReceiver.release();
            imageReceiver = null;
        }

        if (creamEventHandler != null) {
            creamEventHandler.removeAllEvent();
            creamEventHandler = null;
        }
    }

    /**
     * This method is used to show success message on toast
     *
     * @param context Ability context to show toastdialog on screen
     * @param message String message for capture success
     */
    private void showTips(Context context, String message) {
        try {
            terminate();
        } catch (IOException e) {
            e.printStackTrace();
        }
        getUITaskDispatcher().asyncDispatch(() -> {
            showToast(message, context);
        });
    }

    private void showToast(String message, Context context) {
        LayoutScatter layoutScatter = LayoutScatter.getInstance(context);
        Component component = layoutScatter.parse(ResourceTable.Layout_md_toast_layout, null, false);
        Text text = (Text) component.findComponentById(ResourceTable.Id_toast_text);
        text.setText(message);

        if (toastDialog != null) {
            toastDialog.cancel();
            toastDialog = null;
        }
        toastDialog = new ToastDialog(this);
        UiUtil uiUtil = new UiUtil(mContext);
        toastDialog.setContentCustomComponent(component);
        toastDialog.getComponent().setBackground(uiUtil.getShapeElement(ShapeElement.RECTANGLE, ResourceTable.Color_lighter_gray, 0.0f));
        toastDialog.setOffset(30, 100);
        toastDialog.setDuration(1000);
        toastDialog.show();
    }

    @Override
    protected void onBackPressed() {
        super.onBackPressed();
        try {
            terminate();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * CameraStateCallbackImpl
     */
    class CameraStateCallbackImpl extends CameraStateCallback {
        CameraStateCallbackImpl() {
        }

        @Override
        public void onCreated(Camera camera) {
            previewSurface = surfaceProvider.getSurfaceOps().get().getSurface();
            if (previewSurface == null) {
                return;
            }

            // Wait until the preview surface is created.
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            CameraConfig.Builder cameraConfigBuilder = camera.getCameraConfigBuilder();
            cameraConfigBuilder.addSurface(previewSurface);
            cameraConfigBuilder.addSurface(imageReceiver.getRecevingSurface());

            camera.configure(cameraConfigBuilder.build());
            cameraDevice = camera;
        }

        @Override
        public void onConfigured(Camera camera) {
            FrameConfig.Builder framePreviewConfigBuilder = camera.getFrameConfigBuilder(FRAME_CONFIG_PREVIEW);
            framePreviewConfigBuilder.addSurface(previewSurface);
            camera.triggerLoopingCapture(framePreviewConfigBuilder.build());
        }
    }

    /**
     * SurfaceCallBack
     */
    class SurfaceCallBack implements SurfaceOps.Callback {
        @Override
        public void surfaceCreated(SurfaceOps callbackSurfaceOps) {
            if (callbackSurfaceOps != null) {
                callbackSurfaceOps.setFixedSize(SCREEN_HEIGHT, SCREEN_WIDTH);
            }
            openCamera();
        }

        @Override
        public void surfaceChanged(SurfaceOps callbackSurfaceOps, int format, int width, int height) {
        }

        @Override
        public void surfaceDestroyed(SurfaceOps callbackSurfaceOps) {
        }
    }

    /**
     * This method is used to request permission for camera & storage if not given
     */
    private void requestCameraPermission() {
        List<String> permissions = new LinkedList<>(Arrays.asList(SystemPermission.WRITE_USER_STORAGE,
                SystemPermission.READ_USER_STORAGE, SystemPermission.CAMERA));
        permissions.removeIf(
                permission -> verifySelfPermission(permission) == PERMISSION_GRANTED || !canRequestPermission(permission));

        if (!permissions.isEmpty()) {
            requestPermissionsFromUser(permissions.toArray(new String[permissions.size()]), PERMISSION_REQUEST_CODE);
        }
    }

    @Override
    public void onRequestPermissionsFromUserResult(int requestCode, String[] permissions, int[] grantResults) {
        if (requestCode != PERMISSION_REQUEST_CODE) {
            return;
        }
        for (int grantResult : grantResults) {
            if (grantResult != PERMISSION_GRANTED) {
                terminateAbility();
                return;
            }
        }
        restart();
    }
}
